package cn.bitkit.tools.crypto;

import cn.bitkit.base.util.StringUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.asn1.gm.GMObjectIdentifiers;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;

import java.security.*;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;

/**
 *	非对称加密
 * @author 
 *
 */
@Slf4j
public class SM2 {

    public static final String CHARACTER_SET = "UTF-8";
    
	static{
        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
    		Security.addProvider(new BouncyCastleProvider());
        }
	}

    @SneakyThrows
    public static KeyMap createKeyMap(){
        ECGenParameterSpec sm2Spec = new ECGenParameterSpec("sm2p256v1");
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
        kpg.initialize(sm2Spec, new SecureRandom());
        KeyPair keyPair = kpg.generateKeyPair();
        return new KeyMap(keyPair);
    }

    @SneakyThrows
    public static String encrypt(String data, String publicKey){
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(StringUtil.decodeBase64(publicKey));
        KeyFactory keyFactory = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
        PublicKey pubKey = keyFactory.generatePublic(x509EncodedKeySpec);
        byte[] dataBytes = data.getBytes(CHARACTER_SET);
        BCECPublicKey ecPublicKey = (BCECPublicKey) pubKey;
        ECParameterSpec ecSpec = ecPublicKey.getParameters();
        ECDomainParameters ecDomain = new ECDomainParameters(ecSpec.getCurve(), ecSpec.getG(), ecSpec.getN());
        ECPublicKeyParameters ecParameters = new ECPublicKeyParameters(ecPublicKey.getQ(), ecDomain);
        SM2Engine sm2Engine = new SM2Engine();
        sm2Engine.init(true, new ParametersWithRandom(ecParameters, new SecureRandom()));
        byte[] resultBytes = sm2Engine.processBlock(dataBytes, 0, dataBytes.length);
        return StringUtil.encodeBase64(resultBytes);
    }

    @SneakyThrows
    public static String decrypt(String data, String privateKey){
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(StringUtil.decodeBase64(privateKey));
        KeyFactory keyFactory = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
        PrivateKey priKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        BCECPrivateKey sm2PriK = (BCECPrivateKey) priKey;
        ECParameterSpec ecSpec = sm2PriK.getParameters();
        ECDomainParameters ecDomain = new ECDomainParameters(ecSpec.getCurve(), ecSpec.getG(), ecSpec.getN());
        ECPrivateKeyParameters ecParameters = new ECPrivateKeyParameters(sm2PriK.getD(), ecDomain);
        SM2Engine sm2Engine = new SM2Engine();
        sm2Engine.init(false, ecParameters);
        byte[] dataBytes = StringUtil.decodeBase64(data);
        byte[] resultBytes = sm2Engine.processBlock(dataBytes, 0, dataBytes.length);
        return new String(resultBytes, CHARACTER_SET);
    }


    @SneakyThrows
	public static String sign(String data, String privateKey) {
        PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(StringUtil.decodeBase64(privateKey));
        KeyFactory keyFactory = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
        PrivateKey priKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
        Signature signature = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString());
        signature.initSign(priKey);
        signature.update(data.getBytes(CHARACTER_SET));
        return StringUtil.encodeBase64(signature.sign());
	}

	public static boolean verify(String data, String publicKey, String signValue) {
		try {
            X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(StringUtil.decodeBase64(publicKey));
            KeyFactory keyFactory = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
            PublicKey pubKey = keyFactory.generatePublic(x509EncodedKeySpec);
            Signature signature = Signature.getInstance(GMObjectIdentifiers.sm2sign_with_sm3.toString());
			signature.initVerify(pubKey);
			signature.update(data.getBytes(CHARACTER_SET));
			return signature.verify(StringUtil.decodeBase64(signValue));
	    } catch (Exception e) {
            log.error("verify:{}", e.getMessage());
	    }
        return false;
	}

}
